﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Threading;

namespace Xceed.Silverlight.Data
{
  internal class LocalGroupDataRangeEnumerator : DataRangeEnumeratorBase
  {
    #region Static Fields

    private static readonly Guid GetAllRangeCallId = DataUtils.CreateCallId();

    #endregion

    internal LocalGroupDataRangeEnumerator(
      IEnumerable<DataRange> ranges,
      IEnumerable<GroupDescription> groupDescriptions,
      IEnumerable<SortDescription> sortDescriptions,
      IEnumerable<SortDescription> primaryKeys,
      Type userObjectType,
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> dataSourceEnumeratorCreator,
      Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> intermediateEnumeratorCreator,
      Dispatcher dispatcher )
    {
      if( dataSourceEnumeratorCreator == null )
        throw new ArgumentNullException( "dataSourceEnumeratorCreator" );

      if( intermediateEnumeratorCreator == null )
        throw new ArgumentNullException( "intermediateEnumeratorCreator" );

      m_groupDescriptions = groupDescriptions;
      m_sortDescriptions = sortDescriptions;
      m_primaryKeys = primaryKeys;
      m_userObjectType = userObjectType;
      m_enumeratorCreator = intermediateEnumeratorCreator;
      m_sourceEnumerator = new DataRangeEnumerator( ranges, dataSourceEnumeratorCreator );
      m_dataRanges = ranges.ToList();
      m_dataSourceEnumeratorCreator = dataSourceEnumeratorCreator;
      m_dispatcher = dispatcher;
    }

    #region HasMore Property

    protected override bool HasMore
    {
      get
      {
        return ( !m_disposed ) && ( m_hasMore );
      }
    }

    #endregion

    public override void Reset()
    {
      this.CheckState();

      // Get back at start.
      m_hasMore = true;

      if( m_groupedEnumerator != null )
      {
        m_groupedEnumerator.Reset();
      }

      if( m_sourceEnumerator != null )
      {
        m_sourceEnumerator.Reset();
      }
    }

    protected override void Dispose( bool disposing )
    {
      base.Dispose( disposing );

      // Release the source enumerator.
      if( m_sourceEnumerator != null )
      {
        m_sourceEnumerator.Dispose();
        m_sourceEnumerator = null;
      }

      // Release the grouped enumerator.
      if( m_groupedEnumerator != null )
      {
        m_groupedEnumerator.Dispose();
        m_groupedEnumerator = null;
      }

      m_enumeratorCreator = null;
      m_groupDescriptions = null;
      m_sortDescriptions = null;
      m_primaryKeys = null;
      m_dispatcher = null;

      m_hasMore = false;
      m_disposed = true;
    }

    protected override IAsyncResult BeginGetNextCore( AsyncCallback callback, object state )
    {
      // The data in the ranges have been all fetched.  We may now return them
      // grouped and sorted.
      if( m_groupedEnumerator != null )
      {
        return m_groupedEnumerator.BeginGetNext( callback, state );
      }
      // In order to group and sort the data, we need to fetch all the ranges first.
      else
      {
        return this.GetAllRange( callback, state );
      }
    }

    protected override GetNextResult EndGetNextCore( IAsyncResult asyncResult )
    {
      // Simulate the end of the source if the enumerator has been disposed.
      if( m_disposed )
        return new GetNextResult();

      // We are enumerating the local result.
      GetNextResult result;
      if( !asyncResult.UseCallId( LocalGroupDataRangeEnumerator.GetAllRangeCallId ) )
      {
        result = m_groupedEnumerator.EndGetNext( asyncResult );
        if( result == null )
        {
          result = new GetNextResult();
        }

        m_hasMore = result.Success;
      }
      // We have fetched all the desired data ranges on the data source.
      else
      {
        IEnumerable userObjects = asyncResult.GetCallResult<IEnumerable>();
        DataSourceProvider dataSource = new ListDataSourceProvider( userObjects );
        dataSource.Init( m_dispatcher );

        m_groupedEnumerator = this.CreateGroupedDataRangeEnumerator( dataSource );
        m_groupedEnumerator.Reset();

        // We must return the first item.
        IAsyncResult syncResult = m_groupedEnumerator.BeginGetNext( null, null );

        // The result should be returned synchronously since we're using a SyncDataSourceProvider.
        Debug.Assert( syncResult.CompletedSynchronously );

        result = m_groupedEnumerator.EndGetNext( syncResult );
      }

      return result;
    }

    private IAsyncResult GetAllRange( AsyncCallback callback, object state )
    {
      AsyncResult asyncResult = new AsyncResult(
                                  LocalGroupDataRangeEnumerator.GetAllRangeCallId,
                                  callback,
                                  state );
      List<DataPath> dataPaths = new List<DataPath>();

      // Retrieve the first data range that has items.
      int currentRange = 0;
      while( currentRange < m_dataRanges.Count )
      {
        if( m_dataRanges[ currentRange ].IsSelectionRangeInclusive )
          break;

        currentRange++;
      }

      if( currentRange < m_dataRanges.Count )
      {
        Func<AsyncCallback, object, IAsyncResult> beginGetNextItem = null;
        Func<AsyncResult, IAsyncResult, DependentCallResult> endGetNextItem = null;

        m_sourceEnumerator = new DataRangeEnumerator( new DataRange[] { m_dataRanges[ currentRange ] }, m_dataSourceEnumeratorCreator );

        beginGetNextItem = ( cb, st ) => m_sourceEnumerator.BeginGetNext( cb, st );
        endGetNextItem = ( par, iar ) =>
        {
          if( m_disposed )
            return new DependentCallResult( null );

          // Fetch the next data item.
          GetNextResult result = m_sourceEnumerator.EndGetNext( iar );
          if( result.Success )
          {
            // When using a remote data source, we should always receive SelectionRanges
            if( result.IsInInclusiveRange )
            {
              if( !dataPaths.Contains( result.Item ) )
              {
                dataPaths.Add( result.Item );
              }
            }

            return new DependentCallResult( null, beginGetNextItem, endGetNextItem );
          }
          // We have all the desired items from current range.
          else
          {
            while( true )
            {
              currentRange++;

              if( currentRange == m_dataRanges.Count )
              {
                // We won't need the enumerator on the data source anymore.
                m_sourceEnumerator.Dispose();
                m_sourceEnumerator = null;

                IEnumerable userObjects = LocalGroupDataRangeEnumerator.GetUserObjects( dataPaths );
                userObjects = this.GetTypedUserObjects( userObjects );

                return new DependentCallResult( userObjects );
              }
              else
              {
                DataRange nextDataRange = m_dataRanges[ currentRange ];

                if( nextDataRange.IsSelectionRangeInclusive )
                {
                  m_sourceEnumerator.Dispose();
                  m_sourceEnumerator = new DataRangeEnumerator( new DataRange[] { nextDataRange }, m_dataSourceEnumeratorCreator );

                  return new DependentCallResult( null, beginGetNextItem, endGetNextItem );
                }

                this.RemoveUnSelectedDataPaths( nextDataRange, dataPaths );
              }
            }
          }
        };

        asyncResult.Start( beginGetNextItem, endGetNextItem );
      }
      // There is no range to get.
      else
      {
        IEnumerable emptyResultSet = LocalGroupDataRangeEnumerator.GetUserObjects( dataPaths );
        emptyResultSet = this.GetTypedUserObjects( emptyResultSet );

        asyncResult.StartSync( emptyResultSet );
      }

      return asyncResult;
    }

    private void RemoveUnSelectedDataPaths( DataRange currentDataRange, List<DataPath> dataPaths )
    {
      List<DataPath> unSelectedDataPaths = new List<DataPath>();

      foreach( DataPath datapath in dataPaths )
      {
        if( ( currentDataRange != null ) && currentDataRange.Contains( datapath ) )
        {
          unSelectedDataPaths.Add( datapath );
        }
      }
      foreach( DataPath unSelectedDataPath in unSelectedDataPaths )
      {
        dataPaths.Remove( unSelectedDataPath );
      }
    }

    private IDataRangeEnumerator CreateGroupedDataRangeEnumerator( DataSourceProvider dataSource )
    {
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> enumeratorCreator =
        ( groupHandler, sortHandler, filterHandler ) =>
          m_enumeratorCreator.Invoke( dataSource, groupHandler, sortHandler, filterHandler );

      // Create a range that will return the whole source.
      DataRangeContext dataRangeContext = new DataRangeContext(
                                            dataSource.ElementType,
                                            dataSource.PrimaryKeyInfo,
                                            null,
                                            null );
      DataRange dataRange = DataRange.All( true, dataRangeContext );

      return new GroupDataRangeEnumerator(
        new DataRange[] { dataRange },
        m_groupDescriptions,
        m_sortDescriptions,
        dataSource.PrimaryKeyInfo,
        dataSource.ElementType,
        dataSource.GetElementMemberType,
        enumeratorCreator );
    }

    private IEnumerable GetTypedUserObjects( IEnumerable userObjects )
    {
      if( m_userObjectType != typeof( object ) )
      {
        MethodInfo methodInfo = typeof( Enumerable ).GetMethod( "Cast" );
        if( ( methodInfo != null )
          && ( methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().Length == 1 )
          && ( methodInfo.GetParameters().Length == 1 ) )
        {
          methodInfo = methodInfo.MakeGenericMethod( m_userObjectType );
          userObjects = ( IEnumerable )methodInfo.Invoke( null, new object[] { userObjects } );
        }
      }

      return userObjects;
    }

    private static IEnumerable GetUserObjects( IEnumerable<DataPath> dataPaths )
    {
      if( dataPaths == null )
        yield break;

      foreach( DataPath dataPath in dataPaths )
      {
        RawDataItem dataItem = dataPath.LastChild as RawDataItem;
        if( dataItem != null )
        {
          yield return dataItem.Data;
        }
      }
    }

    #region Private Fields

    private IDataRangeEnumerator m_sourceEnumerator;
    private IDataRangeEnumerator m_groupedEnumerator; //null
    private IEnumerable<GroupDescription> m_groupDescriptions;
    private IEnumerable<SortDescription> m_sortDescriptions;
    private IEnumerable<SortDescription> m_primaryKeys;
    private Type m_userObjectType;
    private Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> m_enumeratorCreator;
    private bool m_hasMore = true;
    private bool m_disposed; //false
    private List<DataRange> m_dataRanges;
    private Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> m_dataSourceEnumeratorCreator;
    private Dispatcher m_dispatcher;

    #endregion
  }
}
